<?php
/**
 * ProjectRepo.php
 *
 * @copyright   2023 thingsworld.com.cn - All Rights Reserved
 * @link        http://thingsworld.com.cn/
 * @author      stiffer.chen <chenl@thingsworld.com.cn>
 * @created     2023-04-12 17:33:55
 * @modified    2023-04-12 17:33:55
 *
 */

namespace App\Repositories;

use App\Models\Customer;
use App\Models\DictDataModel;
use App\Models\Product;
use App\Models\Project;
use App\Models\UserModel;
use App\Repositories\Project\TaskRepo;
use App\Services\DashboardService;
use App\Services\TaskService;
use Carbon\Carbon;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;

class ProjectRepo
{
    const STATUS_UN_START = 'un_start';
    const STATUS_STARTED = 'started';
    const STATUS_CLOSED = 'closed';
    const STATUS_COMPLETED = 'completed';
    const STATUS_FILING = 'filing';
    const STATUS_POSTPONE = 'postpone';

    const SCHEDULE_STATUS_UN_START = 'un_start'; // 未开始
    const SCHEDULE_STATUS_UN_DELAY = 'delay'; // 拖延
    const SCHEDULE_STATUS_PROCESSING = 'processing'; // 进行中
    const SCHEDULE_STATUS_POSTPONE = 'postpone'; // 延期
    const SCHEDULE_STATUS_COMPLETE_BEFORE = 'complete_before'; // 提前完成
    const SCHEDULE_STATUS_COMPLETE_EQUAL = 'equal'; // 正常完成
    const SCHEDULE_STATUS_COMPLETE_AFTER = 'complete_after'; // 拖延完成

    const TYPE_OWNER = 'owner';
    const TYPE_CREATOR = 'creator';

    private static $typeTasks = null;

    public static function params()
    {
        $status = self::status();
        $status[] = ['code' => self::STATUS_POSTPONE, 'title' => '延期'];
        $zoneDicts = DictDataModel::query()
            ->from('dict_data as dd')
            ->join('dict as d', 'd.id', '=', 'dd.dict_id')
            ->select(['dd.name'])
            ->where('d.name', '=', '所属区域')
            ->get()
            ->pluck('name')
            ->toArray();
        return [
            'status' => $status,
            'schedule_status' => self::scheduleStatus(),
            'zones' => $zoneDicts
        ];
    }

    public static function status()
    {
        return [
            ['code' => self::STATUS_UN_START, 'title' => '未开始'],
            ['code' => self::STATUS_STARTED, 'title' => '进行中'],
            ['code' => self::STATUS_CLOSED, 'title' => '已关闭'],
            ['code' => self::STATUS_COMPLETED, 'title' => '已完成'],
            ['code' => self::STATUS_FILING, 'title' => '已归档'],
        ];
    }

    public static function scheduleStatus()
    {
        return [
            ['code' => self::SCHEDULE_STATUS_UN_START, 'title' => '未开始'],
            ['code' => self::SCHEDULE_STATUS_UN_DELAY, 'title' => '进度拖延'],
            ['code' => self::SCHEDULE_STATUS_PROCESSING, 'title' => '进行中'],
            ['code' => self::SCHEDULE_STATUS_POSTPONE, 'title' => '进度延期'],
            ['code' => self::SCHEDULE_STATUS_COMPLETE_BEFORE, 'title' => '提前完成'],
            ['code' => self::SCHEDULE_STATUS_COMPLETE_EQUAL, 'title' => '正常完成'],
            ['code' => self::SCHEDULE_STATUS_COMPLETE_AFTER, 'title' => '拖延完成'],
        ];
    }

    // 只取未开始、进行中的
    public static function gantt_projects()
    {
        $isPc = intval(request()->get('is_pc') ?? '');
        $selectStatus = [
            self::STATUS_UN_START,
            self::STATUS_STARTED
        ];
        if (!$isPc) {
            $selectStatus = array_merge($selectStatus, [self::STATUS_CLOSED, self::STATUS_COMPLETED, self::STATUS_POSTPONE]);
        }
        $projects = Project::query()
            ->select(['id', 'name'])
            ->whereIn('status', $selectStatus)
            ->where('mark', '=', 1)
            ->get();
        $data = [];
        if (empty($projects)) {
            return $data;
        }
        foreach ($projects as $project) {
            $data[] = [
                'id' => $project->id,
                'name' => $project->name
            ];
        }
        return $data;
    }

    /**
     * 甘特图数据
     */
    public static function gantt_data()
    {
        $projectId = intval(request()->get('project_id') ?? 0);
        $builder = Project::query()->where('mark', '=', 1);
        $builder->with(['tasks.products', 'products']);
        if ($projectId) {
            $builder->where('id', $projectId);
        }
        $projects = $builder->get();
        if (!$projects->count()) {
            return [];
        }
        $results = [];
        foreach ($projects as $project) {
            $results[] = self::handleGanttDataItem($project);
        }
        return $results;
    }

    public static function over_view()
    {
        $limit = intval(request('limit', 10));
        $builder = self::over_view_builder();
        $results = $builder->paginate($limit);
        $results->transform(function ($item) {
            return self::overViewItem($item);
        });
        return $results;
    }

    private static function overViewItem($item)
    {
        $tasks = $item->tasks;
        // 设备数量
        $productQuantity = $item->products->count();
        // 负责人
        $userId = $item->user->id ?? 0;
        $username = $item->user->realname ?? '';
        // 协作人
        $users = $item->users->toArray();
        $usersArr = [];
        foreach ($users as $itemUser) {
            $usersArr[] = [
                'id' => $itemUser['user']['id'] ?? 0,
                'realname' => $itemUser['user']['realname'] ?? ''
            ];
        }
        $types = \App\Repositories\TaskRepo::getTypes();
        $tasks = self::overViewItemTaskCounts($types, $item);
        return [
            'id' => $item->id,
            'name' => $item->name,
            'order_number' => $item->order_number,
            'product_quantity' => $productQuantity,
            'user_id' => $userId,
            'user_name' => $username,
            'users' => $usersArr,
            'entry_at' => $item->entry_at,
            'task_types' => $types,
            'task_type_counts' => $tasks
        ];
    }

    // 只统计已完成的状态
    private static function overViewItemTaskCounts($types, $item)
    {
        $tasks = $item->tasks->toArray();
        $results = [];
        foreach ($types as $type) {
            $itemCount = 0;
            foreach ($tasks as $task) {
                if ($task['type'] != $type || $task['status'] != \App\Repositories\TaskRepo::STATUS_COMPLETED) {
                    continue;
                }
                $itemCount += $task['plan_product_quantity'];
            }
            $results[] = $itemCount;
        }
        return $results;
    }

    private static function over_view_builder()
    {
        $status = trim(request('status', ''));
        $zoneName = trim(request('zone_name', ''));

        $builder = Project::query()
            ->where('project.mark', 1);
        $builder->with(['user', 'users.user', 'tasks', 'products']);
        if ($status) {
            $builder->where('status', '=', $status);
        }
        if ($zoneName) {
            $builder->where('zone_name', '=', $zoneName);
        }
        $builder->orderByDesc('plan_start_at')
            ->orderByDesc('update_time')
            ->orderByDesc('create_time');
        return $builder;
    }

    /**
     * 项目任务归类
     * 项目指标:
     *  项目名称
     *  负责人
     *  订单号
     *  设备数量
     *  计划工时
     *  实际工时(项目任务填写的工时总和)
     * 任务指标
     *  每天完成的设备数量
     *  计划工时
     *  实际工时
     * @return array
     */
    public static function typeTasks()
    {
        $taskData = \App\Repositories\TaskRepo::specialData();
        $projects = self::getTypeProjects();

        $startAts = []; // 最早开始时间
        $endAts = []; // 最晚结束时间
        $details = [];
        foreach ($projects as $project) {
            $deviceCount = Product::query()->where('project_id', $project['id'])->count();
            $itemId = $project['id']; // 项目ID
            $itemName = $project['name']; // 项目名称
            $itemUserName = $project['user_name']; // 负责人姓名
            $itemOrderNumber = $project['order_number']; // 订单号
            $itemTasks = $taskData[$itemId] ?? []; // 项目的所有任务
            $itemDeviceCount = $deviceCount; // 项目设备数量
            $itemPlanStartAt = $project['plan_start_at']; // 项目开始时间
            $itemPlanCompleteAt = $project['plan_complete_at']; // 项目结束时间
            $itemTaskData = self::getTypeProjectTasks($itemPlanStartAt, $itemPlanCompleteAt, $itemTasks);
            $itemStartAt = $itemTaskData['first_start_at'] ?? '';
            $itemCompleteAt = $itemTaskData['latest_complete_at'] ?? '';
            if ($itemStartAt) {
                $startAts[] = $itemStartAt;
            }
            if ($itemCompleteAt) {
                $endAts[] = $itemCompleteAt;
            }
            $details[] = [
                'id' => $itemId,
                'start_at' => $itemStartAt,
                'complete_at' => $itemCompleteAt,
                'project_name' => $itemName,
                'user_name' => $itemUserName,
                'order_number' => $itemOrderNumber,
                'device_count' => $itemDeviceCount,
                'plan_work_hour' => $itemTaskData['total_plan_work_hour'] ?? 0,
                'work_hour' => $itemTaskData['total_work_hour'] ?? 0,
                'tasks' => $itemTaskData['tasks']
            ];
        }
        sort($startAts);
        rsort($endAts);
        $firstStartAt = $startAts[0] ?? '';
        $latestCompleteAt = $endAts[0] ?? '';

        // 导出
        $export = intval(request('export', ''));
        if ($export) {
            return self::exportTypeTasks($firstStartAt, $latestCompleteAt, $details);
        }

        return [
            'first_start_at' => $firstStartAt,
            'latest_complete_at' => $latestCompleteAt,
            'projects' => $details
        ];
    }

    private static function exportTypeTasks($firstStartAt, $lastCompleteAt, $inputProjects)
    {
        $exportIds = trim(request('export_ids', ''));
        $exportIdArr = [];
        if ($exportIds) {
            $exportIdArr = explode(',', $exportIds);
        }

        $projects = [];
        if ($exportIdArr) {
            foreach ($inputProjects as $inputProject) {
                if (!in_array($inputProject['id'], $exportIdArr)) {
                    continue;
                }
                $projects[] = $inputProject;
            }
        } else {
            $projects = $inputProjects;
        }
        $titles = collect($projects)->pluck('project_name')->toArray();

        $fileName = "gantt";
        $pathPrefix = 'downloads/' . trim($fileName) . '/' . date('Y/m/d');
        $prefix = public_path() . '/' . $pathPrefix;
        $excelFileName = "{$fileName}-" . md5(date('YmdHis') . microtime()) . '.xlsx';
        if (!is_dir($prefix)) {
            mkdir($prefix, 0777, true);
        }
        $excelFilePath = $prefix . '/' . $excelFileName;
        $spreadsheet = new Spreadsheet();

        foreach ($titles as $index => $title) {
            $currentProject = $projects[$index];

            // 整理单个项目导出数据
            $currentStartAt = $currentProject['start_at']; // 开始时间
            $currentCompleteAt = $currentProject['complete_at']; // 结束时间
            $tasks = $currentProject['tasks'] ?? []; // 当前项目的所有任务
            $taskProductQuantity = collect($tasks)->sum('product_quantity'); // 项目所有任务的设备数量
            $projectDeviceCount = $currentProject['device_count'] ? $currentProject['device_count'] : ' 0'; // 项目设备数量
            $diffDays = Carbon::parse($currentStartAt)->diffInDays($currentCompleteAt); // 开始、结束时间相差天数
            $currentSheetData = []; // 当前表格数据
            $currentSheetHeadPrefix = ['名称', '数量', '计划工时', '实际工时']; // 开头四个数据
            $currentSheetHeadDates = [];
            $currentSheetHeadDates[] = $currentStartAt;
            for ($i = 0; $i < $diffDays; $i++) {
                $itemIndex = $i + 1;
                $currentSheetHeadDates[] = Carbon::parse($currentStartAt)->addDays($itemIndex)->toDateString();
            }
            $currentHeader = array_merge($currentSheetHeadPrefix, $currentSheetHeadDates); // 表格头
            $currentSheetData[] = $currentHeader;
            // 任务数据
            $currentProjectData = [
                $currentProject['project_name'],
                $projectDeviceCount,
                ' ' . $currentProject['plan_work_hour'],
                ' ' . $currentProject['work_hour'],
            ];
            $currentSheetData[] = $currentProjectData;

            foreach ($tasks as $task) {
                $currentTaskData = [
                    $task['type'],
                    ' ' . $task['product_quantity'],
                    ' ' . $task['plan_work_hour'],
                    ' ' . $task['work_hour']
                ];
                $taskItems = $task['items'] ?? [];
                foreach ($currentSheetHeadDates as $date) {
                    if (isset($taskItems[$date])) {
                        $currentDate = ' ' . $taskItems[$date];
                    } else {
                        $currentDate = '';
                    }
                    $itemTaskData = $currentDate;
                    $currentTaskData[] = $itemTaskData;
                }
                $currentSheetData[] = $currentTaskData;
            }

            // 添加表格
            $sheet = new Worksheet($spreadsheet, $title);
            // 表格注入数据
            $sheet->fromArray($currentSheetData);
            $spreadsheet->addSheet($sheet);
        }
        $spreadsheet->removeSheetByIndex(0);
        // 生成文件并返回url
        $writer = new Xlsx($spreadsheet);
        $writer->save($excelFilePath);
        return env('APP_URL') . '/' . $pathPrefix . '/' . $excelFileName;
    }

    /**
     * 任务归类的单个项目的所有任务的指标
     *
     * @param $projectPlanStartAt
     * @param $projectPlanCompleteAt
     * @param $tasks
     * @return array
     */
    public static function getTypeProjectTasks($projectPlanStartAt, $projectPlanCompleteAt, $tasks)
    {
        $taskTypes = \App\Repositories\TaskRepo::getTypes();
        $tasks = collect($tasks)->filter(function ($item) use ($taskTypes) {
            return in_array($item['type'], $taskTypes);
        })->groupBy('type')->toArray();

        // 总计划工时
        $totalPlanWorkHour = 0;
        // 总实际工时
        $totalWorkHour = 0;
        $types = \App\Repositories\TaskRepo::getTypes();

        $firstStartAts = [];
        $latestCompleteAts = [];

        $typeTasks = [];
        foreach ($types as $index => $type) {
            $currentTasks = $tasks[$type] ?? [];
            $itemTotalPlanWorkHour = collect($currentTasks)->sum('plan_work_hour'); // 当前计划工时
            $totalPlanWorkHour += $itemTotalPlanWorkHour; // 计划工时
            $itemTotalWorkHour = collect($currentTasks)->sum('work_hour'); // 当前实际工时
            $totalWorkHour += $itemTotalWorkHour; // 实际工时
            $itemProductQuantity = collect($currentTasks)->sum('plan_product_quantity'); // 设备数量

            $activeTasks = collect($currentTasks)->filter(function ($item) {
                return $item['start_at'] != '' && $item['complete_at'] != '';
            })->toArray();

            $columns = array_column($activeTasks, 'complete_at');
            array_multisort($columns, SORT_ASC, $activeTasks);

            $currentTaskItems = [];
            foreach ($activeTasks as $activeTask) {
                $firstStartAts[] = $activeTask['start_at'];
                $latestCompleteAts[] = $activeTask['complete_at'];
                $itemProductTotal = intval($currentTaskItems[$activeTask['complete_at']] ?? 0);
                $currentTaskItems[$activeTask['complete_at']] = $itemProductTotal + $activeTask['plan_product_quantity'];
            }
            $typeTasks[] = [
                'index' => $index,
                'type' => $type,
                'plan_work_hour' => $itemTotalPlanWorkHour,
                'work_hour' => $itemTotalWorkHour,
                'product_quantity' => $itemProductQuantity,
                'items' => $currentTaskItems
            ];
        }
        sort($firstStartAts);
        rsort($latestCompleteAts);
        return [
            'first_start_at' => $firstStartAts[0] ?? '', // 最早开始时间
            'latest_complete_at' => $latestCompleteAts[0] ?? '', // 最晚完成时间
            'total_plan_work_hour' => $totalPlanWorkHour, // 总计划工时
            'total_work_hour' => $totalWorkHour, // 总实际工时
            'tasks' => $typeTasks // 明细
        ];
    }

    public static function getTypeProjects()
    {
        $projectId = intval(request('project_id', ''));
        $builder = Project::query()
            ->select([
                'project.id',
                'project.name',
                'project.plan_work_hour',
                'project.order_number',
                'project.plan_start_at',
                'project.start_at',
                'project.plan_complete_at',
                'project.complete_at',
                'user.realname'
            ])
            ->join('user', 'user.id', '=', 'project.user_id')
            ->where('project.mark', 1)
            ->whereIn('project.status', [self::STATUS_UN_START, self::STATUS_STARTED]);
        if ($projectId) {
            $builder->where('project.id', $projectId);
        }
        $builder->with('products');
        $results = $builder->get();
        $results->transform(function ($item) {
            return [
                'id' => $item->id,
                'name' => $item->name,
                'plan_work_hour' => $item->plan_work_hour,
                'order_number' => $item->order_number,
                'plan_start_at' => $item->plan_start_at,
                'start_at' => $item->start_at,
                'plan_complete_at' => $item->plan_complete_at,
                'complete_at' => $item->complete_at,
                'schedule_text' => $item->schedule_text,
                'user_name' => $item->realname,
            ];
        });
        return $results->toArray();
    }

    // 未开始、进行中
    public static function simpleData()
    {
        $builder = Project::query()
            ->select(['id', 'name'])
            ->whereIn('status', [self::STATUS_UN_START, self::STATUS_STARTED])
            ->where('mark', 1);
        $result = $builder->get();
        $result->transform(function ($project) {
            return [
                'id' => $project->id,
                'name' => $project->name
            ];
        });
        return $result;
    }

    private static function handleGanttDataItem($project)
    {
        $projectId = $project->id;
        $projectName = $project->name;
        $projectNumber = $project->project_number;
        $projectDeviceCount = $project->products->count();
        $projectPlanStartAt = $project->plan_start_at;
        $projectPlanCompleteAt = $project->plan_complete_at;
        $projectStartAt = $project->start_at;
        if (!$projectStartAt) {
            $projectStartAt = '--';
        }
        $projectCompleteAt = $project->complete_at;
        if (!$projectCompleteAt) {
            $projectCompleteAt = '--';
        }
        $projectStatus = $project->status;
        $projectStatusText = $project->status_text;
        $projectSchedule = $project->schedule;
        $projectScheduleText = $project->schedule_text;

        $tasks = [];
        foreach ($project->tasks as $task) {
            $taskId = $task->id;
            $taskName = $task->name;
            $taskNumber = $task->task_number;
            $taskType = $task->type;
            $taskDeviceCount = $task->products->count();
            $taskPlanStartAt = $task->plan_start_at;
            $taskPlanCompleteAt = $task->plan_complete_at;
            $taskStartAt = $task->start_at;
            $taskCompleteAt = $task->complete_at;
            $taskSchedule = $task->schedule;
            $taskScheduleText = $task->schedule_text;
            $tasks[] = [
                'id' => $taskId,
                'name' => $taskName,
                'task_number' => $taskNumber,
                'type' => $taskType,
                'device_count' => $taskDeviceCount,
                'plan_start_at' => $taskPlanStartAt,
                'plan_complete_at' => $taskPlanCompleteAt,
                'start_at' => $taskStartAt,
                'complete_at' => $taskCompleteAt,
                'schedule' => $taskSchedule,
                'schedule_text' => $taskScheduleText
            ];
        }

        $result = [
            'id' => $projectId,
            'name' => $projectName,
            'project_number' => $projectNumber,
            'device_count' => $projectDeviceCount,
            'plan_start_at' => $projectPlanStartAt,
            'plan_complete_at' => $projectPlanCompleteAt,
            'start_at' => $projectStartAt,
            'complete_at' => $projectCompleteAt,
            'schedule' => $projectSchedule,
            'schedule_text' => $projectScheduleText,
            'tasks' => $tasks
        ];
        return $result;
    }

    public static function reStart($id)
    {
        $project = Project::find($id);
        if (empty($project) || !$project->mark) {
            throw new \Exception('项目不存在');
        }
        if ($project->status == self::STATUS_FILING) {
            throw new \Exception('已归档项目不能重新开始');
        }
        if ($project->status == self::STATUS_STARTED) {
            throw new \Exception('已开始项目不能重新开始');
        }

        $project->start_at = Carbon::now()->toDateString();
        $project->status = self::STATUS_STARTED;
        $project->complete_at = '';
        $project->saveOrFail();

        $history = new Project\History([
            'status' => self::STATUS_STARTED,
            'user_id' => registry('user_id')
        ]);
        $project->histories()->save($history);
        return self::fetch($project->id);
    }

    public static function start($id)
    {
        $project = Project::find($id);
        if (empty($project)) {
            throw new \Exception('项目不存在');
        }
        if (!$project->show_start) {
            throw new \Exception('当前状态不允许进行 开始 操作');
        }
        $startAt = trim(request()->get('start_at') ?? '');
        if (!$startAt) {
            throw new \Exception('请填写项目开始时间');
        }
        try {
            $startAt = Carbon::parse($startAt)->toDateString();
        } catch (\Exception $e) {
            throw new \Exception('错误的开始时间(格式错误)');
        }
        $project->start_at = $startAt;
        $project->status = self::STATUS_STARTED;
        $project->saveOrFail();

        $history = new Project\History([
            'status' => self::STATUS_STARTED,
            'user_id' => registry('user_id')
        ]);
        $project->histories()->save($history);
        return self::fetch($project->id);
    }

    public static function close($id)
    {
        $project = Project::find($id);
        if (empty($project)) {
            throw new \Exception('项目不存在');
        }
        if (!$project->show_close) {
            throw new \Exception("当前状态: (" . $project->status_text . "), 不允许关闭");
        }
        $closedAt = request()->get('closed_at') ?? '';
        try {
            $closedAt = Carbon::parse($closedAt)->toDateString();
        } catch (\Exception $e) {
            throw new \Exception('错误的关闭时间(格式错误)');
        }

        $project->closed_at = $closedAt;
        $project->status = self::STATUS_CLOSED;
        $project->saveOrFail();

        $history = new Project\History([
            'status' => self::STATUS_CLOSED,
            'user_id' => registry('user_id')
        ]);
        $project->histories()->save($history);

        $tasks = $project->tasks();
        if ($tasks->count()) {
            foreach ($tasks as $task) {
                TaskRepo::close($task->id);
            }
        }
    }

    public static function destroy($id)
    {
        try {
            $detail = self::fetch($id);
            $projectId = $detail['id'];
            $project = Project::find($projectId);
            if (empty($project)) {
                throw new \Exception('项目不存在');
            }
            if (!$project->show_delete) {
                throw new \Exception('该状态不允许进行删除操作');
            }
            $taskCount = $project->tasks->count();
            if ($taskCount) {
                throw new \Exception('该项目下存在任务请先删除任务');
            }
            $project->mark = 0;
            $project->saveOrFail();
        } catch (\Exception $e) {
            throw new \Exception($e->getMessage());
        }
    }

    public static function store($id = 0)
    {
        $data = self::requestData($id);
        if ($id) {
            $project = Project::find($id);
        } else {
            $project = new Project();
        }
        if (empty($project)) {
            $project = new Project();
        }

        $status = $data['status'];

        $project->fill($data);
        $project->saveOrFail();

        $userIds = $data['user_ids'] ?? [];
        $projectUsers = [];
        if ($userIds) {
            foreach ($userIds as $userId) {
                $projectUsers[] = new Project\User([
                    'user_id' => $userId
                ]);
            }
        }

        $project->users()->delete();
        if ($projectUsers) {
            $project->users()->saveMany($projectUsers);
        }

        $history = new Project\History();
        $history->status = $status;
        $history->user_id = registry('user_id');
        $project->histories()->save($history);
        return self::fetch($project->id);
    }

    public static function products($projectId)
    {
        $project = Project::find($projectId);
        if (empty($project)) {
            throw new \Exception("项目不存在(#{$projectId})");
        }
        $data = request()->getContent();
        $arr = json_decode($data, true);
        if (empty($arr)) {
            throw new \Exception('请选择要关联的设备');
        }
        $productIds = $arr['product_ids'] ?? [];
        if (empty($productIds)) {
            throw new \Exception('请选择要关联的设备');
        }

        foreach ($productIds as $productId) {
            $product = Product::find($productId);
            if (empty($product)) {
                throw new \Exception("设备信息不存在(#{$productId})");
            }
            if ($product->project_id) {
                throw new \Exception("设备已绑定项目(#{$productId})");
            }
        }

        foreach ($productIds as $productId) {
            $product = Product::find($productId);
            if (empty($product)) {
                continue;
            }
            $product->project_id = $projectId;
            $product->saveOrFail();
        }
    }

    public static function complete($projectId)
    {
        $project = Project::find($projectId);
        if (empty($project)) {
            throw new \Exception('项目不存在');
        }

        if (!$project->show_complete) {
            throw new \Exception('该状态不允许进行完成操作');
        }

        $completeAt = trim(request()->get('complete_at') ?? '');
        if (empty($completeAt)) {
            throw new \Exception('请输入完成时间');
        }
        try {
            $completeAt = Carbon::parse($completeAt)->toDateString();
        } catch (\Exception $e) {
            throw new \Exception('完成时间格式错误');
        }

        $status = self::STATUS_COMPLETED;
        $project->status = $status;
        $project->complete_at = $completeAt;
        $project->saveOrFail();

        $history = new Project\History([
            'status' => $status,
            'user_id' => registry('user_id')
        ]);
        $project->histories()->save($history);
        return self::fetch($project->id);
    }

    public static function filing($projectId)
    {
        $project = Project::find($projectId);
        if (empty($project)) {
            throw new \Exception('项目不存在');
        }
        if (!$project->show_filling) {
            throw new \Exception('该状态不允许进行归档操作');
        }

        $status = self::STATUS_FILING;
        $project->status = $status;
        $project->saveOrFail();

        $history = new Project\History([
            'status' => $status,
            'user_id' => registry('user_id')
        ]);
        $project->histories()->save($history);
        return self::fetch($project->id);
    }

    private static function getXAxis()
    {
        $date = trim(request('date', ''));
        try {
            $date = Carbon::parse($date)->toDateString();
        } catch (\Exception $e) {
            $date = Carbon::now()->toDateString();
        }

        $start = Carbon::parse($date)->startOfWeek()->toDateString();
        return [
            $start,
            Carbon::parse($start)->addDays(1)->toDateString(),
            Carbon::parse($start)->addDays(2)->toDateString(),
            Carbon::parse($start)->addDays(3)->toDateString(),
            Carbon::parse($start)->addDays(4)->toDateString(),
            Carbon::parse($start)->addDays(4)->toDateString(),
            Carbon::parse($start)->addDays(6)->toDateString(),
        ];
    }

    // 项目任务完成数量统计: 默认为近7天
    public static function taskCompleteCounts($projectId)
    {
        $project = Project::find($projectId);
        if (empty($project)) {
            throw new \Exception("项目不存在");
        }
        $tasks = $project->tasks;
        $types = \App\Repositories\TaskRepo::getTypes();
        if (!$types) {
            return [];
        }
        $dateType = DashboardService::DATE_TYPE_WEEK;
        $monthStart = '';
        $monthEnd = '';

        $xAxis = self::getXAxis();

        $result = [
            'types' => $types,
            'labels' => $xAxis
        ];

        $data = self::taskCompleteCountsWeek($tasks, $xAxis, $monthStart, $monthEnd);
        $result['data'] = $data;
        return $result;
    }

    private static function taskCompleteCountsYear($tasks, $labels, $monthStart, $monthEnd)
    {
        $result = [];
        $types = \App\Repositories\TaskRepo::getTypes();
        foreach ($labels as $label) {
            $start = Carbon::parse($label)->startOfMonth()->toDateString();
            $end = Carbon::parse($label)->endOfMonth()->toDateString();

            $labelCounts = [];
            foreach ($types as $type) {
                $tempCount = 0;
                foreach ($tasks as $task) {
                    $taskProductCount = $task->products->count();

                    $iCount = 0;

                    $taskType = $task->type;
                    $taskCompleteAt = $task->complete_at;
                    if ($type == $taskType && $taskCompleteAt >= $start && $taskCompleteAt <= $end) {
                        $iCount = $taskProductCount;
                    }
                    $tempCount += $iCount;
                }
                $labelCounts[] = $tempCount;
            }
            $result[] = $labelCounts;
        }
        return $result;
    }

    private static function taskCompleteCountsQuarter($tasks, $labels, $monthStart, $monthEnd)
    {
        $result = [];
        $types = \App\Repositories\TaskRepo::getTypes();
        foreach ($labels as $label) {
            $start = Carbon::parse($label)->startOfMonth()->toDateString();
            $end = Carbon::parse($label)->endOfMonth()->toDateString();

            $labelCounts = [];
            foreach ($types as $type) {
                $tempCount = 0;
                foreach ($tasks as $task) {
                    $taskProductCount = $task->products->count();

                    $iCount = 0;

                    $taskType = $task->type;
                    $taskCompleteAt = $task->complete_at;
                    if ($type == $taskType && $taskCompleteAt >= $start && $taskCompleteAt <= $end) {
                        $iCount = $taskProductCount;
                    }
                    $tempCount += $iCount;
                }
                $labelCounts[] = $tempCount;
            }
            $result[] = $labelCounts;
        }
        return $result;
    }

    private static function taskCompleteCountsMonth($tasks, $labels, $monthStart, $monthEnd)
    {
        $result = [];
        $types = \App\Repositories\TaskRepo::getTypes();
        $start = Carbon::now()->startOfMonth()->toDateString() . ' 00:00:00';
        $weeks = [
            Carbon::parse($start)->toDateString(),
            Carbon::parse($start)->addDays(7)->toDateString(),
            Carbon::parse($start)->addDays(14)->toDateString(),
            Carbon::parse($start)->addDays(21)->toDateString()
        ];
        foreach ($weeks as $label) {
            $start = Carbon::parse($label)->startOfWeek()->toDateString();
            $end = Carbon::parse($label)->endOfWeek()->toDateString();
            $labelCounts = [];
            foreach ($types as $type) {
                $tempCount = 0;
                foreach ($tasks as $task) {
                    $taskProductCount = $task->products->count();

                    $iCount = 0;

                    $taskType = $task->type;
                    $taskCompleteAt = $task->complete_at;
                    if ($type == $taskType && $taskCompleteAt >= $start && $taskCompleteAt <= $end) {
                        $iCount = $taskProductCount;
                    }
                    $tempCount += $iCount;
                }
                $labelCounts[] = $tempCount;
            }
            $result[] = $labelCounts;
        }
        return $result;
    }

    private static function taskCompleteCountsWeek($tasks, $labels, $monthStart, $monthEnd)
    {
        $result = [];
        $types = \App\Repositories\TaskRepo::getTypes();
        foreach ($labels as $label) {
            $start = Carbon::parse($label)->toDateString();
            $end = Carbon::parse($label)->toDateString();
            $labelCounts = [];
            foreach ($types as $type) {
                $tempCount = 0;
                foreach ($tasks as $task) {
                    $taskProductCount = $task->products->count();

                    $iCount = 0;

                    $taskType = $task->type;
                    $taskCompleteAt = $task->complete_at;
                    if ($type == $taskType && $taskCompleteAt >= $start && $taskCompleteAt <= $end) {
                        $iCount += 1;
                    }
                    $tempCount += $iCount;
                }
                $labelCounts[] = $tempCount;
            }
            $result[] = $labelCounts;
        }
        return $result;
    }

    private static function taskCompleteCountsMonthSelect($tasks, $labels, $monthStart, $monthEnd)
    {
        $result = [];

        $types = \App\Repositories\TaskRepo::getTypes();

        $typeKeys = [];
        foreach ($types as $type) {
            $typeKeys[] = "{$type}_count";
        }
        foreach ($labels as $label) {
            $start = Carbon::parse($label)->startOfMonth()->toDateString();
            $end = Carbon::parse($label)->endOfMonth()->toDateString();

            $labelCounts = [];
            foreach ($typeKeys as $typeKey) {
                $type = str_replace("_count", "", $typeKey);
                $tempCount = 0;
                foreach ($tasks as $task) {
                    $taskProductCount = $task->products->count();

                    $iCount = 0;

                    $taskType = $task->type;
                    $taskCompleteAt = $task->complete_at;
                    if ($type == $taskType && $taskCompleteAt >= $start && $taskCompleteAt <= $end) {
                        $iCount = $taskProductCount;
                    }
                    $tempCount += $iCount;
                }
                $labelCounts[] = $tempCount;
            }
            $result[] = $labelCounts;
        }
        return $result;
    }

    private static function getMonthInput()
    {
        $type = self::getDateType();
        $monthStart = trim(request('month_start', ''));
        $monthEnd = trim(request('month_end', ''));

        if ($type == 'month_select') {
            if (!$monthStart || !$monthEnd) {
                throw new \Exception('请选择开始月份和结束月份');
            }
        }

        if ($monthStart) {
            try {
                $monthStart = Carbon::parse($monthStart);
                $year = $monthStart->year;
                $month = $monthStart->month;
                if ($month < 10) {
                    $month = "0" . $month;
                }
                $monthStart = "{$year}-{$month}";
            } catch (\Exception $e) {
                throw new \Exception('开始月份格式错误');
            }
        }

        if ($monthEnd) {
            try {
                $monthEnd = Carbon::parse($monthEnd);
                $year = $monthEnd->year;
                $month = $monthEnd->month;
                if ($month < 10) {
                    $month = "0" . $month;
                }
                $monthEnd = "{$year}-{$month}";
            } catch (\Exception $e) {
                throw new \Exception('结束月份格式错误');
            }
        }

        if ($monthStart > $monthEnd) {
            throw new \Exception('开始月份不能大于结束月份');
        }

        return [
            'month_start' => $monthStart,
            'month_end' => $monthEnd
        ];
    }

    private static function getDateType()
    {
        $dataTypes = [
            'week',
            'month',
            'quarter',
            'year',
            'month_select'
        ];
        $dateType = strtolower(trim(request('date_type', '')));
        if (!in_array($dateType, $dataTypes)) {
            $dateType = 'week';
        }
        return $dateType;
    }

    public static function schedule_total_task($projectId)
    {
        $project = Project::find($projectId);
        if (empty($project)) {
            throw new \Exception('项目不存在');
        }

        $tasks = $project->tasks;
        $unStartCount = $delayCount = $processingCount = $postponeCount = $completeBeforeCount = $completeEqualCount = $completeAfterCount = 0;

        foreach ($tasks as $task) {
            $schedule = $task->schedule;
            if ($schedule == self::SCHEDULE_STATUS_UN_START) {
                $unStartCount += 1;
            }
            if ($schedule == self::SCHEDULE_STATUS_UN_DELAY) {
                $delayCount += 1;
            }
            if ($schedule == self::SCHEDULE_STATUS_PROCESSING) {
                $processingCount += 1;
            }
            if ($schedule == self::SCHEDULE_STATUS_POSTPONE) {
                $postponeCount += 1;
            }
            if ($schedule == self::SCHEDULE_STATUS_COMPLETE_BEFORE) {
                $completeBeforeCount += 1;
            }
            if ($schedule == self::SCHEDULE_STATUS_COMPLETE_EQUAL) {
                $completeEqualCount += 1;
            }
            if ($schedule == self::SCHEDULE_STATUS_COMPLETE_AFTER) {
                $completeAfterCount += 1;
            }
        }

        return [
            'un_start_count' => $unStartCount,
            'delay_count' => $delayCount,
            'processing_count' => $processingCount,
            'postpone_count' => $postponeCount,
            'complete_before_count' => $completeBeforeCount,
            'complete_equal_count' => $completeEqualCount,
            'complete_after_count' => $completeAfterCount
        ];
    }

    public static function total_task($projectId)
    {
        $project = Project::find($projectId);
        if (empty($project)) {
            throw new \Exception('项目不存在');
        }
        $tasks = $project->tasks->groupBy('status')->toArray();
        $completeCount = $startCount = $unStartCount = 0;
        foreach ($tasks as $status => $task) {
            if ($status == self::STATUS_COMPLETED) {
                $completeCount += count($task);
            }
            if ($status == self::STATUS_STARTED) {
                $startCount += count($task);
            }
            if ($status == self::STATUS_UN_START) {
                $unStartCount += count($task);
            }
        }
        return [
            'complete_count' => $completeCount,
            'start_count' => $startCount,
            'un_start_count' => $unStartCount
        ];
    }

    /**
     * 项目工期统计
     *      已施工天数: 当前日期 - 项目实际开始日期
     *      剩余天数: 项目计划结束时间 - 当前时间(延期, 即超出计划完成时间为0)
     *      计划天数
     *      超出天数: 当前时间 - 计划完成时间 最小为0
     *
     * @param $projectId
     * @return array
     * @throws \Exception
     */
    public static function work_day($projectId)
    {
        $project = Project::find($projectId);
        if (empty($project)) {
            throw new \Exception('项目不存在');
        }
        $projectStartAt = $project->start_at;
        $projectPlanCompleteAt = $project->plan_complete_at;
        // 项目计划天数
        $planWorkDay = $project->plan_work_day;
        // 已施工天数
        $workDay = 0;
        // 剩余天数
        $residueDay = 0;
        // 超出天数
        $beyondDay = 0;
        // 未开始
        if (!$projectStartAt) {
            return [
                'work_day' => $workDay,
                'residue_day' => $residueDay,
                'plan_work_day' => $planWorkDay,
                'beyond_day' => $beyondDay
            ];
        }

        $workDay = Carbon::now()->diffInDays($projectStartAt, true);
        if (Carbon::now()->toDateString() > $projectPlanCompleteAt && $project->status != self::STATUS_COMPLETED) {
            $residueDay = 0;
        } else {
            $residueDay = Carbon::now()->diffInDays($projectPlanCompleteAt, true);
        }

        $beyondDay = Carbon::now()->diffInDays($projectPlanCompleteAt, true);
        if (Carbon::now()->toDateString() < $projectPlanCompleteAt) {
            $beyondDay = 0;
        }

        return [
            'work_day' => $workDay,
            'residue_day' => $residueDay,
            'plan_work_day' => $planWorkDay,
            'beyond_day' => $beyondDay
        ];
    }

    // 汇总
    public static function projects_collect()
    {
        // @todo 暂停
        $projectName = trim(request('project_name', ''));
        $dateStart = trim(request('date_start', ''));
        $dateEnd = trim(request('date_end', ''));

        $types = (new TaskService())->types();

    }

    // 燃尽图数量
    public static function burnDown($id)
    {
        $project = Project::find($id);
        if (empty($project)) {
            throw new \Exception('项目不存在');
        }
        // 实际开始时间
        $startAt = $project->start_at;
        if (!$startAt) {
            $startAt = '--';
        }
        // 实际完成时间
        $completeAt = $project->complete_at;
        if (!$completeAt) {
            $completeAt = '--';
        }
        // 计划工时
        $planWorkHour = $project->plan_work_hour;
        // 计划开始时间
        $planStartAt = $project->plan_start_at;
        if (!$planStartAt) {
            $planStartAt = '--';
        }

        // 计划完成时间
        $planCompleteAtDate = $project->plan_complete_at;
        if (!$planCompleteAtDate) {
            $planCompleteAtDate = '--';
        }

        $tasks = $project->tasks->where('status', \App\Repositories\TaskRepo::STATUS_COMPLETED)->toArray();
        $columns = array_column($tasks, 'complete_at');
        array_multisort($columns, SORT_ASC, $tasks);
        $tasks = collect($tasks)->groupBy('complete_at')->toArray();

        $residueWorkHour = $planWorkHour;
        $residueWorkHours = [];
        foreach ($tasks as $completeAt => $task) {
            $date = $completeAt;
            $itemResidueWorkHour = 0;
            foreach ($task as $item) {
                $itemResidueWorkHour += $item['plan_work_hour'];
            }
            $residueWorkHour -= $itemResidueWorkHour;
            $residueWorkHours[] = [
                'date' => $date,
                'work_hour' => $residueWorkHour
            ];
        }
        return [
            'start_at' => $startAt,
            'complete_at' => $completeAt,
            'plan_start_at' => $planStartAt,
            'plan_complete_at' => $planCompleteAtDate,
            'plan_work_hour' => $planWorkHour,
            'residue_work_hours' => $residueWorkHours
        ];
    }

    public static function product_list($id)
    {
        $perPage = (int)request('limit');
        if ($perPage < 1) {
            $perPage = 1;
        }

        $project = Project::find($id);
        if (empty($project) || !$project->mark) {
            throw new \Exception('项目不存在');
        }

        $builder = Product::query()->with(['type', 'customer'])->where('project_id', $id);
        $type = trim(request('type', ''));
        $completeTaskProductIds = self::completeTaskProductIds($id, $type);

        // 剔除已完成任务的设备
        if ($completeTaskProductIds) {
            $builder->whereNotIn('product.id', $completeTaskProductIds);
        }

        $products = $builder->paginate($perPage);
        $products->transform(function ($item) {
            $temp = [];
            $temp['id'] = $item->id;
            $temp['name'] = $item->name;
            $temp['model'] = $item->model;
            $temp['warranty_start'] = $item->warranty_start;
            $temp['type'] = $item->type->name ?? '';
            $temp['customer_id'] = $item->customer->id ?? 0;
            $temp['customer_name'] = $item->customer->name ?? '';
            $temp['product_number'] = $item->product_number ?? '';
            $temp['date_buy'] = $item->date_buy ?? '';
            return $temp;
        });
        return $products;
    }

    // 项目已完成的任务类型所使用的商品ID集合
    public static function completeTaskProductIds($id, $type)
    {
        $id = intval($id);
        $type = trim($type);
        if (!$id || !$type) {
            return [];
        }
        $builder = Project\Task::query()
            ->select(['tp.product_id'])
            ->from('task as t')
            ->join('task_product as tp', 'tp.task_id', '=', 't.id')
            ->where('t.project_id', '=', $id)
            ->where('t.type', '=', $type);
        $productIds = $builder->get()->pluck('product_id')->toArray();
        return array_unique($productIds);
    }

    public static function getList()
    {
        $perPage = (int)request('limit');
        if ($perPage < 1) {
            $perPage = 1;
        }

        $name = trim(request()->get('name') ?? '');
        $projectNumber = trim(request()->get('project_number') ?? '');
        $provinceId = intval(request()->get('province_id') ?? 0);
        $cityId = intval(request()->get('city_id') ?? 0);
        $countyId = intval(request()->get('county_id') ?? 0);

        $builder = Project::query()->with(['customer', 'user', 'create_user', 'tasks', 'products']);
        $builder->where('mark', 1);
        $status = request('status', '');
        $type = request('type', '');

        $dateStart = trim(request('date_start', ''));
        $dateEnd = trim(request('date_end', ''));

        if ($dateStart) {
            try {
                $dateStart = Carbon::parse($dateStart)->toDateString();
            } catch (\Exception $e) {
                $dateStart = '';
            }
        }

        if ($dateEnd) {
            try {
                $dateEnd = Carbon::parse($dateEnd)->toDateString();
            } catch (\Exception $e) {
                $dateEnd = '';
            }
        }

        if ($dateStart) {
            $dateStart = Carbon::parse($dateStart . ' 00:00:00')->timestamp;
        }
        if ($dateEnd) {
            $dateEnd = Carbon::parse($dateEnd . ' 23:59:59')->timestamp;
        }

        $currentUserId = registry('user_id');
        if ($currentUserId != 1) {
            if ($type == self::TYPE_OWNER) {
                $builder->where('project.user_id', $currentUserId);
            }
            if ($type == self::TYPE_CREATOR) {
                $builder->where('project.create_user_id', $currentUserId);
            }
        }
        if ($status) {
            if ($status == self::STATUS_POSTPONE) {
                $builder->where('status', self::STATUS_STARTED)
                    ->where('plan_complete_at', '<=', Carbon::now()->toDateString());
            } else {
                $builder->where('status', $status);
            }
        }
        if ($name) {
            $builder->where('project.name', 'like', "%{$name}%");
        }
        if ($projectNumber) {
            $builder->where('project.project_number', '=', $projectNumber);
        }
        if ($provinceId) {
            $builder->where('province_id', $provinceId);
        }
        if ($cityId) {
            $builder->where('city_id', $cityId);
        }
        if ($countyId) {
            $builder->where('county_id', $countyId);
        }
        if ($dateStart) {
            $builder->where('project.create_time', '>=', $dateStart);
        }
        if ($dateEnd) {
            $builder->where('project.create_time', '<=', $dateEnd);
        }
        $builder->orderByDesc('create_time');
        $result = $builder->paginate($perPage);
        $result->transform(function ($item) {
            return self::getDetail($item);
        });
        return $result;
    }

    public static function fetch($id)
    {
        $project = Project::with(['customer', 'user', 'create_user', 'tasks', 'products'])->find($id);
        if (empty($project) || !$project->mark) {
            throw new \Exception('项目不存在');
        }
        return self::getDetail($project);
    }

    private static function getDetail($project)
    {
        $planStartAt = $project->plan_start_at;
        $planCompleteAt = $project->plan_complete_at;
        $planDay = Carbon::parse($planStartAt)->diffInDays($planCompleteAt, true);

        $tasks = $project->tasks;
        $taskCount = $tasks->count();
        $productCount = $project->products->count();
        $createUser = $project->create_user;
        $percent = self::percent($project);
        // 实际工期: 实际结束时间 - 实际开始时间, 未结束时，显示 --
        $workDay = '--';

        $startAt = $project->start_at;
        $completeAt = $project->complete_at;
        if ($startAt && $completeAt) {
            $workDay = Carbon::parse($startAt)->diffInDays($completeAt, true);
        }

        $spent = 0;

        if ($startAt) {
            $spent = Carbon::parse($startAt)->diffInHours(Carbon::now(), true);
        }

        $residue = 0;
        if ($planCompleteAt) {
            $residue = Carbon::parse($planCompleteAt)->diffInHours(Carbon::now());
        }

        $entryAt = $project->entry_at ?? '';

        $users = $project->users;

        $usersData = [];
        foreach ($users as $item) {
            $usersData[] = [
                'user_id' => $item->user_id,
                'user_name' => $item->user->realname
            ];
        }

        $tasks = $project->tasks;
        $spent = $tasks->sum('work_hour'); // 已耗费工时: 项目任务的实际耗费工时

        $result = [
            'id' => $project->id,
            'name' => $project->name,
            'customer_id' => $project->customer_id,
            'customer_name' => $project->customer->name ?? '',
            'province_id' => $project->province_id,
            'province_name' => $project->province_name,
            'city_id' => $project->city_id,
            'city_name' => $project->city_name,
            'county_id' => $project->county_id,
            'county_name' => $project->county_name,
            'address' => $project->address,
            'status' => $project->status,
            'status_text' => $project->status_text,
            'description' => $project->description,
            'plan_start_at' => $planStartAt,
            'zone_name' => $project->zone_name,
            'start_at' => $startAt,
            'plan_complete_at' => $planCompleteAt,
            'complete_at' => $completeAt,
            'entry_at' => $entryAt,
            'plan_day' => $planDay,
            'schedule' => $project->schedule,
            'schedule_text' => $project->schedule_text,
            'create_user_id' => $project->create_user_id,
            'create_user_name' => $project->create_user->realname ?? '',
            'create_at' => Carbon::parse($project->create_time)->toDateTimeString(),
            'task_count' => $taskCount,
            'product_count' => $productCount,
            'postpone' => $project->postpone,
            'percent_residue' => self::taskPercentResidue($project),
            'percent' => $percent,
            'work_day' => $workDay,
            'plan_work_hour' => $project->plan_work_hour,
            'spent' => $spent,
            'residue' => $residue,
            'show_start' => $project->show_start,
            'show_close' => $project->show_close,
            'show_modify' => $project->show_modify,
            'show_delete' => $project->show_delete,
            'show_complete' => $project->show_complete,
            'show_add' => $project->show_add,
            'show_gantt' => $project->show_gantt,
            'show_restart' => $project->show_restart,
            'show_filling' => $project->show_filling,
            'is_charge_person' => $project->is_charge_person,
            'project_number' => $project->project_number,
            'order_number' => $project->order_number,
            'user_id' => $project->user_id,
            'user_name' => $project->user->realname ?? '',
            'users' => $usersData
        ];
        return $result;
    }

    /**
     * 项目已完成百分比: 项目所有已完成的任务的百分比之和
     *
     * @param $project
     * @return float
     */
    public static function percent($project): float
    {
        $tasks = $project->tasks;
        return $tasks->where('status', \App\Repositories\TaskRepo::STATUS_COMPLETED)->pluck('percent')->sum();
    }

    public static function taskPercentResidue($project)
    {
        $tasks = $project->tasks->where('status', '<>', \App\Repositories\TaskRepo::STATUS_CLOSED);
        $percentResidue = 100 - $tasks->pluck('percent')->sum() ?? 0;
        return round($percentResidue, 2);
    }

    public static function products_unbind()
    {
        $data = request()->getContent();
        $arr = json_decode($data, true);
        $productIds = $arr['product_ids'] ?? [];
        if (!$productIds) {
            return;
        }
        foreach ($productIds as $productId) {
            $product = Product::find($productId);
            if (empty($product)) {
                continue;
            }
            $product->project_id = 0;
            $product->saveOrFail();
        }
    }

    private static function requestData($id)
    {
        $data = request()->getContent();
        $arr = json_decode($data, true);

        $zoneName = trim($arr['zone_name'] ?? '');
        if (!$zoneName) {
            throw new \Exception('请选择项目所属区域');
        }

        $number = self::generateNumber($id);
        $name = trim($arr['name'] ?? '');
        if (!$name) {
            throw new \Exception('请输入项目名称');
        }
        $nameProject = Project::byName($name)->first();
        if (!$id && !empty($nameProject)) {
            throw new \Exception('项目名称已存在，请修改');
        }
        if ($id && !empty($nameProject) && $nameProject->id != $id) {
            throw new \Exception('项目名称已存在，请修改');
        }
        $customerId = intval($arr['customer_id'] ?? 0);
        if (!$customerId) {
            throw new \Exception('请选择项目所属客户');
        }
        $customer = Customer::find($customerId);
        if (empty($customer)) {
            throw new \Exception('客户不存在');
        }
        $userId = intval($arr['user_id'] ?? 0);
        if (!$userId) {
            throw new \Exception('请选择项目负责人');
        }
        $user = UserModel::find($userId);
        if (empty($user)) {
            throw new \Exception('负责人信息不存在');
        }

        $entryAt = trim($arr['entry_at'] ?? '');
        if (empty($entryAt)) {
            throw new \Exception('请填写入场时间');
        }
        try {
            $entryAt = Carbon::parse($entryAt)->toDateString();
        } catch (\Exception $e) {
            throw new \Exception('入场时间格式错误');
        }

        $provinceId = intval($arr['province_id'] ?? 0);
        $cityId = intval($arr['city_id'] ?? 0);
        $countyId = intval($arr['county_id'] ?? 0);
        $zones = get_zones($provinceId, $cityId, $countyId);

        $provinceId = $zones['province_id'];
        $provinceName = $zones['province_name'];
        $cityId = $zones['city_id'];
        $cityName = $zones['city_name'];
        $countyId = $zones['county_id'];
        $countyName = $zones['county_name'];

        $address = trim($arr['address'] ?? '');
        if (!$address) {
            throw new \Exception('请输入施工详细地址');
        }
        $planStartAt = trim($arr['plan_start_at'] ?? '');
        try {
            $planStartAt = Carbon::parse($planStartAt)->toDateString();
        } catch (\Exception $e) {
            throw new \Exception('计划开始时间错误');
        }
        $planCompleteAt = trim($arr['plan_complete_at'] ?? '');
        try {
            $planCompleteAt = Carbon::parse($planCompleteAt)->toDateString();
        } catch (\Exception $e) {
            throw new \Exception('计划完成时间错误');
        }
        if ($planCompleteAt <= $planStartAt) {
            throw new \Exception('计划完成时间不能早于计划开始时间');
        }
        $planWorkHour = floatval($arr['plan_work_hour'] ?? '');
        if (!$id && !$planWorkHour) {
            throw new \Exception('请输入计划工时');
        }
        $status = trim($arr['status'] ?? '');
        if ($id && !$status) {
            throw new \Exception('请选择任务状态');
        }

        $description = trim($arr['description'] ?? '');
        $orderNumber = trim($arr['order_number'] ?? '');

        $data = [
            'zone_name' => $zoneName,
            'project_number' => $number,
            'entry_at' => $entryAt,
            'name' => $name,
            'customer_id' => $customerId,
            'user_id' => $userId,
            'province_id' => $provinceId,
            'province_name' => $provinceName,
            'city_id' => $cityId,
            'city_name' => $cityName,
            'county_id' => $countyId,
            'county_name' => $countyName,
            'address' => $address,
            'plan_start_at' => $planStartAt,
            'plan_complete_at' => $planCompleteAt,
            'create_user_id' => registry('user_id') ?? 0,
            'description' => $description,
            'order_number' => $orderNumber
        ];

        $userIds = [];
        $arrUserIds = $arr['user_ids'] ?? [];
        foreach ($arrUserIds as $arrUserId) {
            $itemUserId = intval($arrUserId);
            $itemUser = UserModel::find($itemUserId);
            if (empty($itemUser)) {
                throw new \Exception("协作人不存在(ID: {$arrUserId})");
            }
            $userIds[] = $itemUserId;
        }

        if (!$id) {
            $data['status'] = self::STATUS_UN_START;
            $data['plan_work_hour'] = $planWorkHour;
        } else {
            $data['status'] = $status;
        }
        $data['user_ids'] = $userIds;
        return $data;
    }

    private static function generateNumber($id)
    {
        $project = Project::find($id);
        if (!empty($project)) {
            return $project->project_number;
        }
        $number = date('Ymd') . rand(100000, 999999);
        $numberProjectCount = Project::byNumber($number)->count();
        if ($numberProjectCount) {
            return self::generateNumber($id);
        }
        return $number;
    }
}